﻿#region Usings

using System;
using System.Globalization;

#endregion

namespace Yandex.Market.Api.YmlSerialization
{
    /// <summary>
    ///   The base for all exception classes of Yandex.Market.Api.YmlSerialization
    /// </summary>
    internal class YmlException : Exception
    {
        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlException" /> class.
        /// </summary>
        public YmlException()
        {}

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlException" /> class.
        /// </summary>
        /// <param name = "message">The message.</param>
        public YmlException(string message) : base(message)
        {}
    }

    /// <summary>
    ///   Raised when the location of serialization specified cannot be
    ///   created or cannot be read from.
    ///   This exception is raised during serialization and deserialization
    /// </summary>
    internal class YmlBadLocationException : YmlException
    {
        #region Constructor

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlBadLocationException" /> class.
        /// </summary>
        /// <param name = "location">The location.</param>
        public YmlBadLocationException(string location)
        {
            Location = location;
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets or sets the bad location which caused the exception
        /// </summary>
        /// <value>The location.</value>
        public string Location { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get { return String.Format("The location specified cannot be read from or written to: {0}", Location); }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when trying to serialize an attribute where 
    ///   another attribute with the same name already exists.
    ///   This exception is raised during serialization.
    /// </summary>
    internal class YmlAttributeAlreadyExistsException : YmlException
    {
        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlAttributeAlreadyExistsException" /> class.
        /// </summary>
        /// <param name = "attrName">Name of the attribute.</param>
        public YmlAttributeAlreadyExistsException(string attrName)
        {
            AttrName = attrName;
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the name of the attribute.
        /// </summary>
        /// <value>The name of the attribute.</value>
        public string AttrName { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get
            {
                return String.Format(CultureInfo.CurrentCulture,
                                     "An attribute with this name already exists: '{0}'.",
                                     AttrName);
            }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when the attribute corresponding to some property is not present in the given XML file, when deserializing.
    ///   This exception is raised during deserialization.
    /// </summary>
    internal class YmlAttributeMissingException : YmlException
    {
        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlAttributeMissingException" /> class.
        /// </summary>
        /// <param name = "attrName">Name of the attribute.</param>
        public YmlAttributeMissingException(string attrName)
        {
            AttributeName = attrName;
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the name of the attribute.
        /// </summary>
        /// <value>The name of the attribute.</value>
        public string AttributeName { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get
            {
                return String.Format(CultureInfo.CurrentCulture,
                                     "No attributes with this name found: '{0}'.",
                                     AttributeName);
            }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when the element value corresponding to some property is not present in the given XML file, when deserializing.
    ///   This exception is raised during deserialization.
    /// </summary>
    internal class YmlElementValueMissingException : YmlException
    {
        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlAttributeMissingException" /> class.
        /// </summary>
        /// <param name = "elementName">Name of the element.</param>
        public YmlElementValueMissingException(string elementName)
        {
            ElementName = elementName;
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the name of the attribute.
        /// </summary>
        /// <value>The name of the attribute.</value>
        public string ElementName { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get
            {
                return String.Format(CultureInfo.CurrentCulture,
                                     "Element with the given name does not contain text values: '{0}'.",
                                     ElementName);
            }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when the element value corresponding to some property is not present in the given XML file, when deserializing.
    ///   This exception is raised during deserialization.
    /// </summary>
    internal class YmlElementValueAlreadyExistsException : YmlException
    {
        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlAttributeMissingException" /> class.
        /// </summary>
        /// <param name = "elementName">Name of the element.</param>
        public YmlElementValueAlreadyExistsException(string elementName)
        {
            ElementName = elementName;
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the name of the attribute.
        /// </summary>
        /// <value>The name of the attribute.</value>
        public string ElementName { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get
            {
                return String.Format(CultureInfo.CurrentCulture,
                                     "Element with the given name already has value: '{0}'.",
                                     ElementName);
            }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when the element corresponding to some property is not present in the given XML file, when deserializing.
    ///   This exception is raised during deserialization.
    /// </summary>
    internal class YmlElementMissingException : YmlException
    {
        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlElementMissingException" /> class.
        /// </summary>
        /// <param name = "elemName">Name of the element.</param>
        public YmlElementMissingException(string elemName)
        {
            ElementName = elemName;
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets or the name of the element.
        /// </summary>
        /// <value>The name of the element.</value>
        public string ElementName { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get { return String.Format(CultureInfo.CurrentCulture, "No elements with this name found: '{0}'.", ElementName); }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when the value provided for some property in the XML input, cannot be 
    ///   converted to the type of the property.
    ///   This exception is raised during deserialization.
    /// </summary>
    internal class YmlBadlyFormedInput : YmlException
    {
        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlBadlyFormedInput" /> class.
        /// </summary>
        /// <param name = "elemName">Name of the element.</param>
        /// <param name = "badInput">The value of the input which could not be converted to the type of the property.</param>
        public YmlBadlyFormedInput(string elemName, string badInput)
        {
            ElementName = elemName;
            BadInput = badInput;
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the name of the element.
        /// </summary>
        /// <value>The name of the element.</value>
        public string ElementName { get; private set; }

        /// <summary>
        ///   Gets the value of the input which could not be converted to the type of the property.
        /// </summary>
        /// <value>The value of the input which could not be converted to the type of the property.</value>
        public string BadInput { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get
            {
                return String.Format(CultureInfo.CurrentCulture,
                                     "The format of the value specified for the property '{0}' is not proper: '{1}'.",
                                     ElementName,
                                     BadInput);
            }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when the value provided for some property in the XML input, cannot be 
    ///   assigned to the property.
    ///   This exception is raised during deserialization.
    /// </summary>
    internal class YmlPropertyCannotBeAssignedTo : YmlException
    {
        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlPropertyCannotBeAssignedTo" /> class.
        /// </summary>
        /// <param name = "propName">Name of the property.</param>
        public YmlPropertyCannotBeAssignedTo(string propName)
        {
            PropertyName = propName;
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the name of the property.
        /// </summary>
        /// <value>The name of the property.</value>
        public string PropertyName { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get { return String.Format(CultureInfo.CurrentCulture, "Could not assign to the property '{0}'.", PropertyName); }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when some member of the collection in the input XML, cannot be added to the collection object.
    ///   This exception is raised during deserialization.
    /// </summary>
    internal class YmlCannotAddObjectToCollection : YmlException
    {
        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlCannotAddObjectToCollection" /> class.
        /// </summary>
        /// <param name = "propName">Name of the property.</param>
        /// <param name = "obj">The object that could not be added to the collection.</param>
        public YmlCannotAddObjectToCollection(string propName, object obj)
        {
            PropertyName = propName;
            ObjectToAdd = obj;
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the name of the property.
        /// </summary>
        /// <value>The name of the property.</value>
        public string PropertyName { get; private set; }

        /// <summary>
        ///   Gets the object that could not be added to the collection.
        /// </summary>
        /// <value>the object that could not be added to the collection.</value>
        public object ObjectToAdd { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get
            {
                return String.Format(CultureInfo.CurrentCulture,
                                     "Could not add object ('{0}') to the collection ('{1}').",
                                     ObjectToAdd,
                                     PropertyName);
            }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when the default value specified by the <c>YmlErrorIfMissedAttribute</c> could not be assigned to the property.
    ///   This exception is raised during deserialization.
    /// </summary>
    internal class YmlDefaultValueCannotBeAssigned : YmlException
    {
        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlDefaultValueCannotBeAssigned" /> class.
        /// </summary>
        /// <param name = "propName">Name of the property.</param>
        /// <param name = "defaultValue">The default value which caused the problem.</param>
        public YmlDefaultValueCannotBeAssigned(string propName, object defaultValue)
        {
            PropertyName = propName;
            TheDefaultValue = defaultValue;
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the name of the property.
        /// </summary>
        /// <value>The name of the property.</value>
        public string PropertyName { get; private set; }

        /// <summary>
        ///   Gets the default value which caused the problem.
        /// </summary>
        /// <value>The default value which caused the problem.</value>
        public object TheDefaultValue { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get
            {
                return String.Format(CultureInfo.CurrentCulture,
                                     "Could not assign the default value specified ('{0}') for the property '{1}'.",
                                     TheDefaultValue,
                                     PropertyName);
            }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when the XML input does not follow standard XML formatting rules.
    ///   This exception is raised during deserialization.
    /// </summary>
    internal class YmlBadlyFormedXml : YmlException
    {
        #region Fields

        /// <summary>
        ///   The inner exception.
        /// </summary>
        private readonly Exception _innerException;

        #endregion

        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlBadlyFormedXml" /> class.
        /// </summary>
        /// <param name = "innerException">The inner exception.</param>
        public YmlBadlyFormedXml(Exception innerException)
        {
            _innerException = innerException;
        }

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlBadlyFormedXml" /> class.
        /// </summary>
        public YmlBadlyFormedXml()
        {}

        #endregion

        #region Properties

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get
            {
                string msg = "The input xml file is not properly formatted!";

                if(_innerException != null)
                    msg += String.Format(CultureInfo.CurrentCulture, "\r\nMore Details:\r\n{0}", _innerException.Message);

                return msg;
            }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when an object cannot be formatted with the format string provided.
    ///   This exception is raised during serialization.
    /// </summary>
    internal class YmlInvalidFormatProvided : YmlException
    {
        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlInvalidFormatProvided" /> class.
        /// </summary>
        /// <param name = "objectType">Type of the fiedl to serialize.</param>
        /// <param name = "format">The format string.</param>
        public YmlInvalidFormatProvided(Type objectType, string format)
        {
            ObjectType = objectType;
            Format = format;
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the type of the field to serialize
        /// </summary>
        /// <value>The type of the field to serialize.</value>
        public Type ObjectType { get; private set; }

        /// <summary>
        ///   Gets the format string.
        /// </summary>
        /// <value>The format string.</value>
        public string Format { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get
            {
                return String.Format(CultureInfo.CurrentCulture,
                                     "Could not format objects of type '{0}' with the format string '{1}'",
                                     ObjectType.Name,
                                     Format);
            }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when trying to serialize self-referential types. This exception cannot be turned off.
    ///   This exception is raised during deserialization.
    /// </summary>
    internal class YmlCannotSerializeSelfReferentialTypes : YmlException
    {
        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlCannotSerializeSelfReferentialTypes" /> class.
        /// </summary>
        /// <param name = "type">The the self-referential type that caused the problem.</param>
        public YmlCannotSerializeSelfReferentialTypes(Type type)
        {
            SelfReferentialType = type;
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the self-referential type that caused the problem.
        /// </summary>
        /// <value>The type of the self-referential type that caused the problem.</value>
        public Type SelfReferentialType { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get
            {
                return String.Format(CultureInfo.CurrentCulture,
                                     "Self Referential types ('{0}') cannot be serialized.",
                                     SelfReferentialType.FullName);
            }
        }

        #endregion
    }

    /// <summary>
    ///   Raised when the object provided for serialization is not of the type provided for the serializer. This exception cannot be turned off.
    ///   This exception is raised during serialization.
    /// </summary>
    internal class YmlObjectTypeMismatch : YmlException
    {
        #region Constructors

        /// <summary>
        ///   Initializes a new instance of the <see cref = "YmlObjectTypeMismatch" /> class.
        /// </summary>
        /// <param name = "expectedType">The expected type.</param>
        /// <param name = "receivedType">The type of the object which did not match the expected type.</param>
        public YmlObjectTypeMismatch(Type expectedType, Type receivedType)
        {
            ExpectedType = expectedType;
            ReceivedType = receivedType;
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the expected type.
        /// </summary>
        /// <value>The expected type.</value>
        public Type ExpectedType { get; private set; }

        /// <summary>
        ///   Gets the type of the object which did not match the expected type.
        /// </summary>
        /// <value>The type of the object which did not match the expected type.</value>
        public Type ReceivedType { get; private set; }

        /// <summary>
        ///   Gets a message that describes the current exception.
        /// </summary>
        /// <returns>
        ///   The error message that explains the reason for the exception, or an empty string("").
        /// </returns>
        public override string Message
        {
            get
            {
                return String.Format(CultureInfo.CurrentCulture,
                                     "Expected an object of type '{0}' but received an object of type '{1}'.",
                                     ExpectedType.Name,
                                     ReceivedType.Name);
            }
        }

        #endregion
    }
}